#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <curses.h>
#include <string.h>
#include "textbuff.h"

#define CTRL(c) ((c) & 037)

#define min(X, Y)                \
     ({ typeof (X) x_ = (X);          \
        typeof (Y) y_ = (Y);          \
        (x_ < y_) ? x_ : y_; })


// Defined constants to be used with the message data-structure
#define EDIT 0
#define SAVE 1
#define QUIT 2
#define DEL  3

// Maximum length of the message passing queue
#define QUEUEMAX 20


// Data structure for message passing queue used to communicate
// between the router and UI thread
struct message_t{
    int data;
    int row;
    int col;
    int command;
} typedef message;

message *mess_q[20];
int numMess = 0; 
int check = 0;
// mutex to control access to the text buffer
pthread_mutex_t text_ = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t check_ = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t message_ = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t full = PTHREAD_COND_INITIALIZER; //Does not mean queue is full/empty just used between pop/push func calls

// The current position of the cursor in the screen
int row;
int col;

// Lines visible in the current view of textbuff
// visible on the screen
int view_min;
int view_max;

/**
 * Removes the first message from the message queue
 * and returns it.
 */
message* pop()
{
	pthread_mutex_lock(&message_);
	while(numMess == 0)
	{
		pthread_cond_wait(&empty,&message_);
		//Do nothing, wait for messages to appear
	}
	//Message Recieved
	message *tmp = mess_q[numMess];
	numMess--;
	
	pthread_cond_signal(&full);
	pthread_mutex_unlock(&message_);
	return tmp;
}
/**
 * Inserts a message at the back of the message queue
 */
void push(message* m_)
{
	pthread_mutex_lock(&message_);
	while(numMess == QUEUEMAX)
	{
		pthread_cond_wait(&full,&message_);
		//Do nothing, too many messages in queue
	}
	//Message Removed 

	numMess++;
	mess_q[numMess] = m_;
	pthread_cond_signal(&empty);
	pthread_mutex_unlock(&message_);
}


/**
 * Redraws the screen with lines min_line -> max_line visible on the screen
 * places the cursor at (r_, c_) when done. If insert_ is 1 then "INPUT MODE"
 * text is displayed otherwise if 0 it isn't.
 */
int redraw(int min_line, int max_line,int r_, int c_, int insert_){
    erase();
    if(max_line - min_line != LINES-1){
        perror("HELP");
        pthread_exit(NULL);
    }
    move(0,0);

    pthread_mutex_lock(&text_);

    for(;min_line < max_line;min_line++){
        char *line = NULL;
        if(getLine(min_line,&line) == 0)
            break;
        int j;
        for(j=0;j < strlen(line);j++){
            addch(*(line+j));
        }
        addch('\n');
    }

    pthread_mutex_unlock(&text_);

    if(insert_){
        standout();
        mvaddstr(LINES-1, COLS-20, "INPUT MODE");
        standend();
    }

    move(r_,c_);
    refresh();
    return 1;
}


/**
 * Input loop of the UI Thread;
 * Loops reading in characters using getch() and placing them into the textbuffer using message queue interface
 */
void input_mode(){
    int c;
    redraw(view_min, view_max, row, col, 1);
    refresh();
    for(;;){
        c = getch();
        if(c == CTRL('D')){
            break;
        }

        int insert_row = row+view_min;
        int insert_col = col;

        //Add code here to insert c into textbuff at (insert_row, insert_col) using the message queue interface.
		message insertM;
		insertM.data = c;
		insertM.row = insert_row;
		insertM.col = insert_col;
		insertM.command = EDIT;
		
		push(&insertM);

        // ------------------------------
        if((col<COLS-1) && (col<LINEMAX-1)){
            col++;
        }else{
            col = 0;


            if(row < LINES - 2){
                row++;
            }else{
                view_min++;
                view_max++;
            }
        }
        redraw(view_min,view_max,row,col,1);
    }
    redraw(view_min,view_max,row,col,0);
}


/**
 * Main loop of the UI thread. It reads in commands as characters
 */
void loop(){
    int c;

    while(1){
        move(row,col);
        refresh();
        c = getch();

        switch(c){
            case 'h':
            case KEY_LEFT:
                if(col > 0)
                    col--;
                else
                    flash();
                break;
            case 'j':
            case KEY_DOWN:
                if(row < LINES -2)
                    row++;
                else
                    if(view_max+1<=getLineLength())
                        redraw(++view_min,++view_max,row,col,0);
                    else
                        flash();
                break;
            case 'k':
            case KEY_UP:
                if(row > 0)
                    row--;
                else
                    if(view_min-1 > -1)
                        redraw(--view_min,--view_max,row,col,0);
                    else
                        flash();
                break;
            case 'l':
            case KEY_RIGHT:
                if((col<COLS-1) && (col<LINEMAX-1))
                    col++;
                else
                    flash();
                break;
            case 'i':
            case KEY_IC:
                input_mode();
                break;
            case 'x':
                flash();

                int del_row = row+view_min;
                int del_col = col;

                // Add code here to delete character (del_row, del_col) from textbuf
				message delM;
				delM.row = del_row;
				delM.col = del_col;
				delM.command = DEL;
		
				push(&delM);
                // ------------------------------

                redraw(view_min,view_max,row,col,0);
                break;
            case 'w':
                flash();

                // Add code here to save the textbuf file
				message saveM;
				saveM.command = SAVE;
		
				push(&saveM);

                // ------------------------------

                break;
            case 'q':
                endwin();
				
                // Add code here to quit the program
				message quitM;
				quitM.command = QUIT;
		
				push(&quitM);
				
				pthread_exit(NULL);
                // ------------------------------
            default:
                flash();
                break;
        }

    }
}


/**
 * Function to be used to spawn the UI thread.
 */
void *start_UI(void *threadid){

    initscr();
    cbreak();
    nonl();
    noecho();
    idlok(stdscr, TRUE);
    keypad(stdscr,TRUE);

    view_min = 0;
    view_max = LINES-1;

    redraw(view_min,view_max,row,col,0);

    refresh();
    loop();
}

/**
 * Function to be used to spawn the autosave thread.
 */
void *autosave(void *threadid)
{
	FILE *fd;
	char *line;
	
    // This function loops until told otherwise from the router thread. Each loop:
	while(1)
	{
		pthread_mutex_lock(&check_);
		if(check == 1)
		{
			break;
		}
		pthread_mutex_unlock(&check_);
        
		if (access("./tempfile.txt~", F_OK) != -1)
			remove("./tempfile.txt~");
		
		// Open the temporary file
		if ((fp = fopen("./tempfile.txt~","w")) == 0)
			perror("An error has occured");
		
        // Read lines from the text buffer and save them to the temporary file
		
		/* psudo code stuff
		I assume we're supposed to use the getLine function to 
		read the line from textbuff but I'm not entirely sure
		
		Then use something like fwrite to write line to temp file
		example:
		char x[10]="ABCDEFGHIJ";
		fwrite(x, sizeof(x[0]), sizeof(x)/sizeof(x[0]), fp);
		*/
		
		pthread_mutex_lock(&text_);
		int index = 0;
		
		while (getLine(index, &line) != 0)
		{
			fprintf(fp, "%s\n", line);
			index++;
			getLine(index, &line);
		}
		pthread_mutex_unlock(&text_);
        // Close the temporary file and sleep for 5 sec.
	//	close(fd);
	//	sleep(5);
	}
	pthread_mutex_unlock(&check_);
	pthread_exit(NULL);
}

int main(int argc, char **argv){

    row = 0;
    col = 0;

    // get text file from argv
	char *file = argv[1];
	
    // set up necessary data structures for message passing queue and initialize textbuff
	
	init_textbuff(file);
	
    // spawn UI thread
	pthread_t t1;
	if(pthread_create(&t1,NULL,start_UI,&t1) != 0)
	{
		perror("Failed To Create UI Thread");
	}
	
    // spawn auto-save thread
	pthread_t t2;
	if(pthread_create(&t2,NULL,autosave,&t2) != 0)
	{
		perror("Failed to Create AutoSave Thread");
	}
	
    // Main loop until told otherwise

	while(1)
	{
        // Recieve messages from the message queue
		message *rec;
		rec = pop();
        // If EDIT then place the edits into the text buffer
		if(rec->command == EDIT)
		{
			pthread_mutex_lock(&text_);
			if(insert(rec->row,rec->col,rec->data) == 0)
			{
				flash();
			}
			pthread_mutex_unlock(&text_);
		}
        // If SAVE then save the file additionally delete the temporary save
		else if(rec->command == SAVE)
		{
			
		}
        // If QUIT then quit the program and tell the appropriate threads to stop
		else if(rec->command == QUIT)
		{
			pthread_mutex_lock(&check_);
			check = 1;
			pthread_mutex_unlock(&check_);
		
		
			pthread_join(t1,NULL);
			pthread_join(t2,NULL);
			
			//To quit loop must then break while loop
			break;
		}
        // If DEL then delete the specified character from the text buffer
		else if(rec->command == DEL)
		{
			pthread_mutex_lock(&text_);
			if(deleteCharacter(rec->row,rec->col) == 0)
			{
				flash();
			}
			pthread_mutex_unlock(&text_);
		}
	}
    // Clean up data structures
	deleteBuffer();
	
	return 1; //Success
}